package com.webchat.ugc.service.redpacket;

import com.webchat.common.constants.RedPacketConstants;
import com.webchat.common.enums.RedisKeyEnum;
import com.webchat.domain.vo.response.RedPacketBaseVO;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.util.concurrent.ThreadLocalRandom;


/**
 * 方案一：基于redisson分布式锁实现
 *
 * 注意：
 * 这里红包剩余金额以及剩余可拆分份额，完全依赖redis（最好缓存失效可以查询保证一致性）
 */
@Service
public class OpenRedPacketWithRedissonLockService extends AbstractOpenRedPacketService {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 采用实时计算红包金额方式
     *
     * @param redPacket
     * @param userId
     * @return
     */
    @Override
    public int openRedPacket(RedPacketBaseVO redPacket, String userId) {

        String lockKey = RedisKeyEnum.LOCK_OPEN_RED_PACKET.getKey(String.valueOf(redPacket.getId()));
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 阻塞等待获取锁
            lock.lock();
            return doOpenRedPacket(redPacket, userId);
        } catch (Exception e) {
            throw e;
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 红包拆分
     *
     * @param redPacket
     * @param userId
     * @return
     */
    private int doOpenRedPacket(RedPacketBaseVO redPacket, String userId) {
        Long redPacketId = redPacket.getId();
        /**
         * 1. 读取当前红包剩余金额
         *
         * 10、10、10
         */
        int balanceAmount = this.getBalance(redPacketId);
        Assert.isTrue(balanceAmount > 0, "红包被瓜分完了");

        /**
         * 2. 获取红包当前剩余可拆分份额
         * 2、2、2
         */
        int gradCount = this.getGradCount(redPacketId);
        Assert.isTrue(gradCount > 0, "来晚了");

        /**
         * 3. 判断红包类型（算法）
         * 3.1 普通红包：非最后一个拆分，平均瓜分（总金额 / 总人数）；最后一个拆分取剩余金额
         * 3.2 拼手气：”二倍均值算法“ 为当前用户随机红包金额
         *
         * 二倍均值红包算法：
         * 示例：100元红包、份10份
         * 1️⃣ 100元 ===> 10000分，保证所有金额为整数（因为我们小数点精确到后两位）
         * 2️⃣ 第1份红包：
         *    均值AVG = 余额 / 剩余份额
         *    2倍均值= 2 * 均值AVG
         *    用于随机生成红包金额范围：[0.01元/1分钱 ～ 2 * 均值AVG] --> randomAmount（1050分）
         *    剩余：9份，余额 10000 - 1050 = 8950分
         * 3️⃣ 第2份红包：
         *    均值AVG = 8950分 / 9
         *    randomAmount（1566分）
         *    剩余：8份，余额 8950分 - 1566分
         *
         * ……
         * 最后一次：
         *    randomAmount = 10000分 - 历史总拆分金额
         *    剩余：0份，余额 0
         *
         */
        // 单位为分
        int openAmount = 0;
        if (gradCount == 1) {
            // 最后一个可拆分名额
            openAmount = balanceAmount;
        } else {
            // 二倍均值
            BigDecimal avgAmount = new BigDecimal(balanceAmount).divide(new BigDecimal(gradCount), 0, BigDecimal.ROUND_HALF_UP);
            BigDecimal avgAmount2 = avgAmount.multiply(new BigDecimal(2));
            int maxVal = avgAmount2.intValue();
            // 在[minVal, maxVal]之间随机一个红包金额：openAmount
            openAmount = ThreadLocalRandom.current().nextInt(RedPacketConstants.MIN_AMOUNT, maxVal + 1);
        }
        /**
         * 4. 写刷新红包剩余金额
         */
        balanceAmount = balanceAmount - openAmount;
        // 刷新当前红包剩余可拆分金额
        this.setBalance(redPacketId, balanceAmount);
        // 红包剩余可拆分次数原子-1
        this.increxGradCountCache(redPacketId, -1);
        // 将当前用户加入红包拆分用户名单缓存
        super.addUser2OpenRedPacketUsersCache(redPacketId, userId);
        if (balanceAmount == 0) {
            // 红包被拆分完
            super.updateRedPacketStatus(redPacketId, RedPacketConstants.RedPacketStatus.END);
        }
        return openAmount;
    }

    /**
     * 这里余额为啥是整数？
     * 因为我们会讲所用 单位元 转化为 分，同时保证元的小数点精确到后两位。
     *
     * @return 单位：分，1块钱，这里返回100分
     */
    private Integer getBalance(Long redPacketId) {
        String balanceKey = RedisKeyEnum.RED_PACKET_BALANCE_COUNT.getKey();
        String balanceCache = redisService.hget(balanceKey, String.valueOf(redPacketId));
        return Integer.valueOf(balanceCache);
    }


    /**
     * 刷新红包剩余可拆分金额
     *
     * @param redPacketId
     * @param balance 单位需要转为分
     */
    private void setBalance(Long redPacketId, Integer balance) {
        String balanceKey = RedisKeyEnum.RED_PACKET_BALANCE_COUNT.getKey();
        redisService.hset(balanceKey, String.valueOf(redPacketId), String.valueOf(balance),
                RedisKeyEnum.RED_PACKET_BALANCE_COUNT.getExpireTime());
    }

    /**
     * 当前已瓜分数量
     *
     * @param redPacketId
     * @return
     */
    private Integer getGradCount(Long redPacketId) {
        String balanceKey = RedisKeyEnum.RED_PACKET_GRAD_COUNT.getKey();
        String countCache = redisService.hget(balanceKey, String.valueOf(redPacketId));
        return Integer.valueOf(countCache);
    }

    /**
     *
     *
     *
     * @param redPacketId
     * @return
     */
    private void increxGradCountCache(Long redPacketId, long count) {
        String balanceKey = RedisKeyEnum.RED_PACKET_GRAD_COUNT.getKey();
        redisService.hIncrementVal(balanceKey, String.valueOf(redPacketId), count);
    }

    /**
     * 初始化红包剩余可拆分金额
     *
     * @param redPacketId
     * @param balance 单位需要转为分
     */
    public void initBalanceCache(Long redPacketId, Integer balance) {
        String balanceKey = RedisKeyEnum.RED_PACKET_BALANCE_COUNT.getKey();
        redisService.hset(balanceKey, String.valueOf(redPacketId), String.valueOf(balance),
                RedisKeyEnum.RED_PACKET_BALANCE_COUNT.getExpireTime());
    }

    /**
     *  初始化红包剩余可以瓜分数量
     *
     * @param redPacketId
     * @return
     */
    public void initGradCountCache(Long redPacketId, int gradCount) {
        String balanceKey = RedisKeyEnum.RED_PACKET_GRAD_COUNT.getKey();
        redisService.hset(balanceKey, String.valueOf(redPacketId), gradCount + "",
                RedisKeyEnum.RED_PACKET_GRAD_COUNT.getExpireTime());
    }

}
